Conversation
|
DRAFT_COMMENT |
|
Hi @Ark0N, it looks like your comment here came through as the placeholder text "DRAFT_COMMENT", so I think the actual review didn't post. Happy to pick it up whenever you get a chance to re-send it. |
|
Thanks for this, @aakhter, and sorry for the "DRAFT_COMMENT" placeholder that went out here on 10-01. That was a glitch in my review tooling, and this is the review that should have been posted. This PR stops a linked case on an unreachable network mount from freezing the server. What needs another round is how callers read a non-answer. Today "did not answer in 1.5 s" and "refused by the cap" both come back as Must fix
Should fix
Small ones
Once items 1 and 2 are in with their tests, this gets another review, and 3 and 4 are one-line swaps that can ride the same push. Thanks again for chasing this one down. The freeze is real, and the hard part of the fix is already done well. |
…t cannot freeze the server A linked case can live on a network mount. When that mount goes away, a hard mount makes stat() wait indefinitely, and the existsSync() probes in the case routes and the workspace hook/statusline helpers ran on the event loop, so a single GET /api/cases (or a session create in that workspace) froze the whole web server until the mount came back. Add boundedPathExists() (src/utils/bounded-path-probe.ts): an async stat that answers "absent" after 1.5 s, shares one in-flight probe per path, remembers a timed-out path until its stat finally settles, and refuses to start new probes while two stalled ones still hold libuv threadpool workers. Route the read-side probes in case-routes.ts and hooks-config.ts through it. The settings writers in hooks-config.ts use an async lstat that treats only ENOENT as missing, so an unreachable workspace is never mistaken for an empty one and has its settings recreated.
…all cap
The bounded path probe answered "absent" both when a path did not exist and
when it simply did not answer, so a stalled linked case 404'd and the Run
button scaffolded a stray local case over it, and two stalled paths anywhere
made every unrelated path read as absent (hooks skipped, statusLine
overridden, the clone warning lost).
- probePath()/probePathKind() are tri-state: present (or directory/file),
absent (ENOENT/ENOTDIR only) and unknown (timeout, other errors, refusal).
boundedPathExists() stays as the display-only boolean.
- A stalled path takes only its own mount out of probing (deepest mount
point from /proc/self/mounts, never /; just the path itself when there is
no mount table). Unrelated paths keep probing. The process-wide cap is a
backstop that answers unknown, and a single-path user request can probe
past it ({ pastCap: true }), still bounded and still recorded as stalled.
One console.warn when a path first stalls and one when the cap engages.
- GET /api/cases/:name keeps NOT_FOUND for definite absence only. An
unreachable linked case answers with its registered path and
unreachable: true; a local one answers OPERATION_FAILED. runClaude and
runShell create a case only on errorCode NOT_FOUND. The case list keeps an
unreachable linked case, marked unreachable, instead of dropping it, and
fix-plan reports an unreadable plan as an error, not "no plan".
- applyWorkspaceHooks and the statusLine helpers skip only a workspace that
is absent or on the stalled mount; a capacity refusal no longer stops
hooks being installed elsewhere, and an unreadable settings file never
lets the exporter override a user's own statusLine.
- The clone flow's repo-settings warning is back on its synchronous check,
and stripCaseEnvKeys uses pathExistsForWrite.
- POST /api/sessions (workingDir) and POST /api/quick-start (case folder)
probe with the bounded probe instead of statSync/existsSync. Missing and
non-directory keep INVALID_INPUT; unknown is OPERATION_FAILED, and
quick-start never scaffolds over a folder that did not answer.
- PATH_PROBE_TIMEOUT_MS and MAX_STALLED_PATH_PROBES move to
src/config/path-probe.ts, overridable via CODEMAN_PATH_PROBE_TIMEOUT_MS
(default 1500) and CODEMAN_PATH_PROBE_MAX_STALLED (default 3), and are
documented in the Settings Reference.
- The probe is exported from the utils barrel and imported from there.
cf9748e to
d1bfbb4
Compare
|
Thanks for the careful review, and no worries at all about the DRAFT_COMMENT placeholder; this was well worth the wait. The throwaway tests on my own route harness made both must-fix items very concrete. I've rebased onto 1.34.0 and pushed one new commit on top of the original (d1bfbb4):
The title and description now cover session creation too. Every new test failed before its fix, and reverting each of the fixes for items 1, 2, 3 and 5 makes its tests fail. Typecheck, lint, format, the frontend-syntax and browser-exclude checks, and the test files touching these modules all pass. |
|
Thanks for the quick and careful turnaround, @aakhter. This PR makes every probe of a linked case folder or session workingDir bounded and tri-state, so an unreachable network mount can no longer freeze the server. All seven items from the last round are in, each with a test, and typecheck, lint, format, the frontend checks and the full test suite pass here. Three edge cases in the new mount-scoping and 1. Under the stall cap, 2.
Please give 3. The stall scope cannot see through symlinks ( Two small ones that can ride along:
Once 1 to 3 are in with their tests, this is ready to merge. Thanks again: the freeze is real, and the probe itself (timers, in-flight sharing, no stray rejections) is in good shape. |
A linked case can live on a network mount. When that mount goes away, a hard mount makes
stat()wait indefinitely. The synchronous probes in the case routes, the workspace hook and statusLine helpers, and session creation (POST /api/sessionsworkingDir,POST /api/quick-startcase folder) then froze the whole server.The probe. The fix is a bounded, tri-state path probe (
src/utils/bounded-path-probe.ts): present, absent (ENOENT/ENOTDIR only) or unknown (no answer within the timeout, another error, or refused).Callers never treat "unknown" as "absent".
GET /api/cases/:namekeeps NOT_FOUND for definite absence. An unreachable linked case comes back with its registered path andunreachable: true, and the Run button creates a case only on NOT_FOUND.Tuning. The timeout and the stall cap live in
src/config/path-probe.ts, overridable withCODEMAN_PATH_PROBE_TIMEOUT_MS(default 1500) andCODEMAN_PATH_PROBE_MAX_STALLED(default 3). A slow but healthy mount, such as an sshfs that needs a couple of seconds on first touch, can raise the timeout.Tests
GET /api/cases/:nameon the timeout path, fix-plan, clone, and both session-create routes. They simulate a hard mount as a frozen synchronous call plus a never-settling async stat.